#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cctype>
#include <unordered_map>
#include <jsoncpp/json/json.h>

#include "Util.hpp"
#include "index.hpp"

namespace ns_searcher
{
    struct InvertedElemOutPut
    {
        uint64_t file_id;                // 对应文件的id
        std::vector<std::string> words;  // 关键字
        int weight;                      // 权重

        InvertedElemOutPut()
            : weight(0)
        { }
    };

    class Searcher
    {
    public:
        Searcher():index(nullptr) {}
        ~Searcher() {};

        // 初始化索引函数
        bool InitSearcher(const std::string& input)
        {
            // 1.获取索引对象
            index = ns_index::Index::GetInstance();
            std::cout << "成功创建index单例" << std::endl;
            // 2.根据input建立索引
            index->BuildIndex(input);
            std::cout << "成功创建正排和倒排索引" << std::endl;
            return true;
        }

        // 执行索引函数
        bool Search(const std::string& query, std::string* jsionStr)
        {
            // 1. 对query进行分词
            std::vector<std::string> words;
            ns_util::JiebaUtil::CutString(query, &words);

            // 2.对每个分词出来的结果，建立相应的索引
            std::vector<InvertedElemOutPut> inverted_list_all;
            std::unordered_map<uint64_t, InvertedElemOutPut> fileIdMap;
            for(std::string& word : words)
            {
                // 将转换为全小写已达到忽略大小写的目的
                boost::to_lower(word);

                // 获取关键字word对于的倒排拉链
                std::vector<ns_index::InvertedElem>* inverted_list = index->GetInvertedIndex(word);
                if(inverted_list == nullptr) continue;

                // 将倒排拉链插入到inverted_list_all数组中
                // inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());

                // 去除重复的file_id
                for(const auto& iter : *inverted_list)
                {
                    // 如果fild_id存在就直接获取对应InvertedElem，不存在就创建
                    InvertedElemOutPut& elem = fileIdMap[iter.file_id];
                    elem.file_id = iter.file_id;
                    elem.weight += iter.weight;
                    elem.words.push_back(iter.word);
                }
            }

            for(const auto& iter : fileIdMap)
            {
                inverted_list_all.push_back(std::move(iter.second));
            }

            // 3.按照权重对inverted_list_all中的所有InvertedElem元素进行排序
            std::sort(inverted_list_all.begin(), inverted_list_all.end(), \
                [](const InvertedElemOutPut& e1, const InvertedElemOutPut& e2){
                    return e1.weight > e2.weight;
            });

            // 4.构建jsion串
            Json::Value root;
            for(const auto& iter : inverted_list_all)
            {
                // 根据正排索引，获取对于文件的内容
                ns_index::FileInfo* info = index->GetForwardIndex(iter.file_id);

                // 使用jsion库序列化字符串
                Json::Value item;
                item["title"] = info->title;
                item["desc"] = GetContentDescribe(info->content, iter.words[0]);
                item["url"] = info->url;

                // Debug
                // item["file_id"] = std::to_string(info->file_id);
                // item["weight"] = iter.weight;

                root.append(item);
            }

            Json::StyledWriter writer;
            *jsionStr = writer.write(root);

            return true;
        }

    private:
        // .html文件摘要获取函数
        std::string GetContentDescribe(const std::string& content, const std::string& word)
        {
            // 1.查找.html文件中第一次出现word的位置
            auto iter = std::search(content.begin(), content.end(), word.begin(), word.end(), [](int e1, int e2){
                return std::tolower(e1) == std::tolower(e2);
            });

            if(iter == content.end()) {
                return "None Describe";
            }

            size_t pos = std::distance(content.begin(), iter);

            // 2.截取word第一次出现位置前后各100个字节
            // 如果前面不足100字节，那么取到开头位置，如果后面不足100字节，那么取到结尾位置
            size_t prev_step = 100;
            size_t next_step = 100;
            size_t start = 0;
            size_t end = content.size();

            if(pos > prev_step + start) {
                start = pos - prev_step;
            }

            if(pos + next_step < end) {
                end = pos + next_step;
            }

            return content.substr(start, end - start + 1) + "...";
        }

        ns_index::Index *index;
    };
}